﻿using System;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using Mesuro.Common;
using Mesuro.Graphing.Utilities;
using Vector = Mesuro.Common.Vector;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.Generic;

namespace Mesuro.Graphing {
	public class SmithAxes : MultiAxes {
		public const string Key = "Smith Chart";
		public const int AXES_Rho = 0;
		public const int AXES_Impedance = 1;
		public const int AXES_Admittance = 2;

		public static Complex ZFromRho( Complex rho ) {
			return (1 + rho) / (1 - rho);
		}
		public static Complex RhoFromZ( Complex z ) {
			return (z - 1) / (z + 1);
		}

		public static Complex YFromRho( Complex rho ) {
			return (1 - rho) / (1 + rho);
		}
		public static Complex RhoFromY( Complex y ) {
			return (1 - y) / (1 + y);
		}

		#region DP: PlotActiveBackground
		public static readonly DependencyProperty PlotActiveBackgroundProperty = DependencyProperty.RegisterAttached( "PlotActiveBackground", typeof( Brush ), typeof( SmithAxes ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.Inherits, PlotActiveBackgroundChangedStatic ) );
		public Brush PlotActiveBackground {
			get { return (Brush)GetValue( PlotActiveBackgroundProperty ); }
			set { SetValue( PlotActiveBackgroundProperty, value ); }
		}
		public static Brush GetPlotActiveBackground( DependencyObject element ) { return (Brush)element.GetValue( PlotActiveBackgroundProperty ); }
		public static void SetPlotActiveBackground( DependencyObject element, Brush value ) { element.SetValue( PlotActiveBackgroundProperty, value ); }
		private static void PlotActiveBackgroundChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		private static object PlotActiveBackgroundCoerceStatic( DependencyObject sender, object baseValue ) {
			if (baseValue == null) {
				return GetPlotBackground( sender );
			} else
				return baseValue;
		}
		#endregion

		#region DP: SecondaryMajorGridlines
		public static readonly DependencyProperty SecondaryMajorGridlinesProperty = DependencyProperty.RegisterAttached( "SecondaryMajorGridlines", typeof( Pen ), typeof( SmithAxes ), new FrameworkPropertyMetadata( new Pen( Brushes.DarkBlue, 1 ), FrameworkPropertyMetadataOptions.None, SecondaryMajorGridlinesChangedStatic ) );
		public Pen SecondaryMajorGridlines {
			get { return (Pen)GetValue( SecondaryMajorGridlinesProperty ); }
			set { SetValue( SecondaryMajorGridlinesProperty, value ); }
		}
		public static Pen GetSecondaryMajorGridlines( DependencyObject element ) { return (Pen)element.GetValue( SecondaryMajorGridlinesProperty ); }
		public static void SetSecondaryMajorGridlines( DependencyObject element, Pen value ) { element.SetValue( SecondaryMajorGridlinesProperty, value ); }
		private static void SecondaryMajorGridlinesChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			sender.CoerceValue( SecondaryMinorGridlinesProperty );
		}
		#endregion

		#region DP: SecondaryMinorGridlines
		public static readonly DependencyProperty SecondaryMinorGridlinesProperty = DependencyProperty.RegisterAttached( "SecondaryMinorGridlines", typeof( Pen ), typeof( SmithAxes ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, SecondaryMinorGridlinesChangedStatic ) );
		public Pen SecondaryMinorGridlines {
			get { return (Pen)GetValue( SecondaryMinorGridlinesProperty ); }
			set { SetValue( SecondaryMinorGridlinesProperty, value ); }
		}
		public static Pen GetSecondaryMinorGridlines( DependencyObject element ) { return (Pen)element.GetValue( SecondaryMinorGridlinesProperty ); }
		public static void SetSecondaryMinorGridlines( DependencyObject element, Pen value ) { element.SetValue( SecondaryMinorGridlinesProperty, value ); }
		private static object SecondaryMinorGridlinesCoerceStatic( DependencyObject sender, object baseValue ) {
			Pen pn;
			if (baseValue == null && (pn = GetSecondaryMajorGridlines( sender )) != null) {
				Brush br = pn.Brush.Clone();
				br.Opacity *= 0.6;
				return new Pen( br, pn.Thickness * 0.6 );
			}
			return baseValue;
		}
		private static void SecondaryMinorGridlinesChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: ShowAdmittanceContours
		public static readonly DependencyProperty ShowAdmittanceContoursProperty = DependencyProperty.RegisterAttached( "ShowAdmittanceContours", typeof( bool? ), typeof( SmithAxes ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, ShowAdmittanceContoursChangedStatic ) );
		public bool? ShowAdmittanceContours {
			get { return (bool?)GetValue( ShowAdmittanceContoursProperty ); }
			set { SetValue( ShowAdmittanceContoursProperty, value ); }
		}
		public static bool? GetShowAdmittanceContours( DependencyObject element ) { return (bool?)element.GetValue( ShowAdmittanceContoursProperty ); }
		public static void SetShowAdmittanceContours( DependencyObject element, bool? value ) { element.SetValue( ShowAdmittanceContoursProperty, value ); }
		private static void ShowAdmittanceContoursChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: ShowImpedanceContours
		public static readonly DependencyProperty ShowImpedanceContoursProperty = DependencyProperty.RegisterAttached( "ShowImpedanceContours", typeof( bool? ), typeof( SmithAxes ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, ShowImpedanceContoursChangedStatic ) );
		public bool? ShowImpedanceContours {
			get { return (bool?)GetValue( ShowImpedanceContoursProperty ); }
			set { SetValue( ShowImpedanceContoursProperty, value ); }
		}
		public static bool? GetShowImpedanceContours( DependencyObject element ) { return (bool?)element.GetValue( ShowImpedanceContoursProperty ); }
		public static void SetShowImpedanceContours( DependencyObject element, bool? value ) { element.SetValue( ShowImpedanceContoursProperty, value ); }
		private static void ShowImpedanceContoursChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		public double DataRadius {
			get { return scaleMagnitude.Range.Max; }
			set { scaleMagnitude.Range = new Limits( 0, value ); }
		}

		private bool _admittanceDataPlotted = false;
		private bool _impedanceDataPlotted = false;

		public bool ShowingAdmittanceContours { get { return ShowAdmittanceContours ?? _admittanceDataPlotted; } }
		public bool ShowingImpedanceContours { get { return ShowAdmittanceContours ?? (_impedanceDataPlotted || !_admittanceDataPlotted); } }

		private VisibleScale scaleMagnitude = new VisibleScale();

		public new EllipseGeometry PlotArea {
			get { return (EllipseGeometry)base.PlotArea; }
			set { base.PlotArea = value; }
		}

		private Rectangle _selection = new Rectangle();
		protected Visual SelectionShape {
			get { return _selection; }
		}
		public Rectangle Selection {
			get {
				return _selection;
			}
		}

		protected override void OnAddPlot( Plot plot ) {
			SetGraphTransform( plot );
		}

		static SmithAxes() {
			Axes.PlotBackgroundProperty.OverrideMetadata( typeof( SmithAxes ), new FrameworkPropertyMetadata( Axes.PlotBackgroundProperty.DefaultMetadata.DefaultValue, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.Inherits, ( x, y ) => x.CoerceValue( PlotActiveBackgroundProperty ) ) );
			MultiAxes.SetAxisSetDefinitionsForType( typeof( SmithAxes ), false,
				new AxisSetDefinition( "Reflection Coefficient",
						new AxisDefinition( "Reflection (Real)" ), new AxisDefinition( "Reflection (Imaginary)" ) ),

				new AxisSetDefinition( "Impedance",
					new AxisDefinition( "Resistance" ), new AxisDefinition( "Reactance" ) ),

				new AxisSetDefinition( "Admittance",
					new AxisDefinition( "Conductance" ), new AxisDefinition( "Susceptance" ) )
			);
		}
		public SmithAxes()
			: base( 2 ) {
			PlotArea = new EllipseGeometry();
		}

		protected override void OnAxisSetChanged( Plot plot, int oldAxisSet, int newAxisSet ) {
			base.OnAxisSetChanged( plot, oldAxisSet, newAxisSet );
			SetGraphTransform( plot );
		}

		protected void SetGraphTransform( Plot plot ) {
			int axisSet = GetAxisSet( plot );
			switch (axisSet) {
				case AXES_Admittance:
					plot.AxesTransform = new SmithTransformedSeries( true );
					break;

				case AXES_Impedance:
					plot.AxesTransform = new SmithTransformedSeries( false );
					break;

				default:
				case AXES_Rho:
					plot.AxesTransform = null;
					break;
			}
		}

		protected override Size ArrangeOverride( Size graphSize ) {

			Point c = PlotArea.Center = new Point( graphSize.Width / 2, graphSize.Height / 2 );
			double rad = PlotArea.RadiusX = PlotArea.RadiusY = Math.Min( graphSize.Width, graphSize.Height ) / 2;

			scaleMagnitude.NumTicks = (int)(2 * rad / MinimumTickSpacing + 0.5);
			scaleMagnitude.Domain = new Limits( 0, rad );

			foreach (Plot plot in Plots) {
				Limits xl = plot.PlotData.Channels[0].Limits, yl = plot.PlotData.Channels[1].Limits;
				double lt, rt, tp, bm;
				lt = c.X + scaleMagnitude.ToPlotSpace( xl.Min );
				tp = c.Y - scaleMagnitude.ToPlotSpace( yl.Max );
				rt = c.X + scaleMagnitude.ToPlotSpace( xl.Max );
				bm = c.Y - scaleMagnitude.ToPlotSpace( yl.Min );

				plot.Arrange( new Rect( lt, tp, Math.Abs( rt - lt ), Math.Abs( bm - tp ) ) );
			}

			return graphSize;
		}

		protected override void OnRender( DrawingContext cx ) {
			cx.DrawGeometry( PlotActiveBackground, ScalePen, PlotArea );
			cx.PushClip( PlotArea );
			double plotRadius = PlotArea.RadiusX;
			double unitRadius = plotRadius / DataRadius;
			Circ contour = new Circ();
			Point rhoOrigin = PlotArea.Center;
			cx.DrawEllipse( PlotBackground, null, rhoOrigin, unitRadius, unitRadius );
			Point zOrigin = rhoOrigin;
			zOrigin.X += unitRadius;
			Point yOrigin = rhoOrigin;
			yOrigin.X -= unitRadius;
			int minRat = 5;
			for (int i = 0; i < scaleMagnitude.NumTicks * minRat; i++) {
				Pen zPen = (i % minRat == 0) ? MajorGridlines : MinorGridlines;
				Pen yPen = ShowingImpedanceContours ? (i % minRat == 0) ? SecondaryMajorGridlines : SecondaryMinorGridlines : zPen;
				double rho = -DataRadius + i * 2 * DataRadius / scaleMagnitude.NumTicks / minRat;
				//Cannot draw circles with R==0 or R==inf
				if (Math.Abs( rho ) == 1) continue;

				decimal zVal = VisibleScale.Niceify( ZFromRho( (double)rho ).Re, 2 );
				decimal yVal = VisibleScale.Niceify( YFromRho( (double)rho ).Re, 2 );
				double zRho = RhoFromZ( (double)zVal ).Re;
				double yRho = RhoFromY( (double)yVal ).Re;

				//Draw Constant R/G Contour
				contour.Radius = 0.5 * (1 - zRho) * unitRadius;
				if (ShowingImpedanceContours) {
					contour.Centre = new Point( zOrigin.X + contour.Radius, zOrigin.Y );
					cx.DrawEllipse( null, zPen, contour );
					contour.Centre = new Point( zOrigin.X - contour.Radius, zOrigin.Y );
					cx.DrawEllipse( null, zPen, contour );
				}
				contour.Radius = 0.5 * (1 - yRho) * unitRadius;
				if (ShowingAdmittanceContours) {
					contour.Centre = new Point( yOrigin.X - contour.Radius, yOrigin.Y );
					cx.DrawEllipse( null, yPen, contour );
					contour.Centre = new Point( yOrigin.X + contour.Radius, yOrigin.Y );
					cx.DrawEllipse( null, yPen, contour );
				}
				//Draw constant X/B Contour
				contour.Radius = unitRadius / (double)zVal;
				if (ShowingImpedanceContours) {
					contour.Centre = new Point( zOrigin.X, zOrigin.Y + contour.Radius );
					cx.DrawEllipse( null, zPen, contour );
					contour.Centre = new Point( zOrigin.X, zOrigin.Y - contour.Radius );
					cx.DrawEllipse( null, zPen, contour );
				}
				contour.Radius = unitRadius / (double)yVal;
				if (ShowingAdmittanceContours) {
					contour.Centre = new Point( yOrigin.X, yOrigin.Y + contour.Radius );
					cx.DrawEllipse( null, yPen, contour );
					contour.Centre = new Point( yOrigin.X, yOrigin.Y - contour.Radius );
					cx.DrawEllipse( null, yPen, contour );
				}
			}
			cx.Pop();
			cx.DrawLine( ScalePen, new Point( rhoOrigin.X - plotRadius, rhoOrigin.Y ), new Point( rhoOrigin.X + plotRadius, rhoOrigin.Y ) );
			cx.DrawEllipse( null, ScalePen, rhoOrigin, unitRadius, unitRadius );
		}

		protected Vector ToDataSpace( Point coord ) {
			return new Vector(
				scaleMagnitude.ToDataSpace( coord.X ) - PlotArea.Center.X,
				scaleMagnitude.ToDataSpace( coord.Y ) - PlotArea.Center.Y
				);
		}
		protected Point ToPlotSpace( Vector datum ) {
			return new Point(
				PlotArea.Center.X + scaleMagnitude.ToPlotSpace( datum[0] ),
				PlotArea.Center.Y + scaleMagnitude.ToPlotSpace( datum[1] )
				);
		}

		protected override void OnRenderSizeChanged( SizeChangedInfo sizeInfo ) {
			base.OnRenderSizeChanged( sizeInfo );
			InvalidateArrange();
			InvalidateVisual();
		}
	}
}
